#include "Chat.h"
#include "log.h"
#include <stdlib.h>
#include "include/cola_ipc.h"
#include <sys/shm.h>

typedef struct {
    long id;
    char flags;
    char buffer[MENSAJE_TAMANIO_MAX];
} mensaje_t;

long Chat_ObtenerId(unsigned long idConversacion, unsigned long numeroUsuario){
    return numeroUsuario + (idConversacion<<16);
}

static bool Chat_VerificarExistenciaUsuario(long numeroUsuario){
    if(numeroUsuario == 1 ||  numeroUsuario == 2)
	return true;
    return false;
}

static int Chat_SHM(int id){
    key_t key = ftok(FTOK_PATH,id);
    int id_shm = shmget(key, SHM_SIZE, 0666|IPC_CREAT);
    if(id_shm != -1)
	return id_shm;
    perror("Error al obtener la memoria compartida: ");
    exit(EXIT_CREAR_MEMORIA);
}

static void* Chat_MapearSHM(int id){
    void* base = shmat(id, NULL, 0);
    if(base != (void*)-1)
	return base;

    perror("Error al mapear la memoria compartida: ");
    exit(EXIT_MAPEAR_MEMORIA);
}

static void Chat_DesMapearSHM(void* base){
    if(shmdt(base) != -1)
	return;
    perror("Error al desmapear la memoria compartida: ");
    exit(EXIT_MAPEAR_MEMORIA);
}

static int Chat_GetCola(int id){
    key_t key = ftok(FTOK_PATH,id);
    int id_cola = msgget(key, 0666|IPC_CREAT);
    if(id_cola != -1)
	return id_cola;
    perror("Error al abrir la cola.");
    exit(EXIT_ABRIR_COLA);
}

static bool Chat_BuscarUsuarioConectado(long idUsuario){
    
}

ChatHandler* InicializarChat(){

    ChatHandler *ch = (ChatHandler*)malloc(sizeof(ChatHandler));

    ch->idSHMUsuarios = Chat_SHM(SHM_USUARIOS_CONECTADOS);
    ch->idSHMConversaciones = Chat_SHM(SHM_CONVERSACIONES_CREADAS);

    ch->baseUsuarios = Chat_MapearSHM(ch->idSHMUsuarios);
    ch->baseConversaciones = Chat_MapearSHM(ch->idSHMConversaciones);

    return ch;
}

void FinalizarChat(ChatHandler* handler){
    if(!handler){
	log(fileno(stdout), "Error en FinalizarChat: El handler es nulo.\n");
	exit(EXIT_HANDLER_NULO);
    }
    Chat_DesMapearSHM(handler->baseUsuarios);
    Chat_DesMapearSHM(handler->baseConversaciones);
    free(handler);
}

Sesion* ChatIniciarSesion(ChatHandler *handler, unsigned long numeroUsuario){

    if(!Chat_VerificarExistenciaUsuario(numeroUsuario)){
	log(fileno(stdout), "No existe el usuario %ld.\n", numeroUsuario);
	return NULL;
    }

    Sesion *sesion = (Sesion*)malloc(sizeof(Sesion));
    sesion->numeroUsuario = numeroUsuario;
    sesion->idSHMUsuario = Chat_SHM(numeroUsuario+ SHM_BASE_USUARIO);
    sesion->baseMemoria = Chat_MapearSHM(sesion->idSHMUsuario);
    return sesion;
}

void ChatCerrarSesion(Sesion* sesion){
    if(sesion){
	Chat_DesMapearSHM(sesion->baseMemoria);
	memset((void*)sesion, sizeof(Sesion), 0);
	free(sesion);
    }
    else{
	log(fileno(stdout), "Error en ChatCerrarSesion: el handler pasado es nulo. Abortando.");
	exit(EXIT_HANDLER_NULO);
    }
}

long* ChatListarConversacionesDeUsuario(Sesion* sesion, long* lista, int &cantidad){
    if(cantidad < 1)
	return NULL;
    lista[0] = 1;
    cantidad = 1;
    return lista;
}

long* ChatListarUsuariosEnConversacion(ChatHandlerConversacion *conversacion, int &cantidad){
    long* participantes = ((long *)conversacion->baseConversacion)+1;
    int ultimo=0;
    while(participantes[ultimo] != 0)
	ultimo++;

    cantidad = ultimo;
    return participantes;
}

ChatHandlerConversacion* ChatCrearConversacion(){
    
}

static void Chat_AgregarUsuarioAConversacion(ChatHandlerConversacion *conversacion){
    long* participantes = ((long *)conversacion->baseConversacion)+1;
    int i=0;
    while(participantes[i] != 0 && participantes[i] != conversacion->numeroUsuario)
	i++;
    participantes[i] = conversacion->numeroUsuario;
}

ChatHandlerConversacion* ChatAbrirConversacion(unsigned long numeroUsuario, unsigned long idConversacion){
    // if(!Chat_ExisteConversacion(idConversacion)){
    // 	log(fileno(stdout), "Error en ChatAbrirConversacion: Conversacion inexistente.\n");
    // 	return NULL;
    // }

    ChatHandlerConversacion *chc = (ChatHandlerConversacion*)malloc(sizeof(ChatHandlerConversacion));

    chc->numeroUsuario = numeroUsuario;
    chc->idConversacion = idConversacion;

    chc->ultimoMensaje = NULL;
    
    chc->idColaInBox = Chat_GetCola(COLA_INBOX);
    chc->idColaOutBox = Chat_GetCola(COLA_OUTBOX);
    chc->idColaEditor = Chat_GetCola(COLA_EDITOR);

    chc->idSHMConversacion = Chat_SHM(idConversacion + SHM_BASE_CONVERSACION);
    chc->baseConversacion = Chat_MapearSHM(chc->idSHMConversacion);

    ((long*)chc->baseConversacion)[0] = 0;

    Chat_AgregarUsuarioAConversacion(chc);

    return chc;
}

static void Chat_SacarUsuarioDeConversacion(ChatHandlerConversacion* conversacion){
    long* participantes = ((long *)conversacion->baseConversacion)+1;
    int ultimo=0;

    while(participantes[ultimo+1] != 0)
	ultimo++;


    int buscado = 0;
    while(participantes[buscado] != conversacion->numeroUsuario && buscado < ultimo)
	buscado++;
    
    if(buscado != ultimo){
	participantes[buscado] = participantes[ultimo];
	participantes[ultimo] = 0;
    }
}

void ChatCerrarConversacion(ChatHandlerConversacion* conversacion){
    if(conversacion){

	((long*)conversacion->baseConversacion)[0] = 1;

	Chat_DesMapearSHM(conversacion->baseConversacion);
	
	if(conversacion->ultimoMensaje)
	    free(conversacion->ultimoMensaje);

	Chat_SacarUsuarioDeConversacion(conversacion);

	memset((void*)conversacion, sizeof(ChatHandlerConversacion), 0);
	free(conversacion);
    }
    else{
	log(fileno(stdout), "Error en ChatCerrarConversacion: el handler pasado es nulo. Abortando.");
	exit(EXIT_HANDLER_NULO);
    }
}

char* ChatObtenerMensajeAEnviar(ChatHandlerConversacion* conversacion, char* buffer, unsigned int &tamanio){
}

static char* Chat_RecibirMensaje(int id_cola, long idDestino, unsigned int &tamanio){
    mensaje_t recibido;
    recibido.id = idDestino;
    recibido.flags = FLAG_MAS_DATOS;
    char *buffer = NULL;
    int bytesRecibidos = 0;
    while(recibido.flags == FLAG_MAS_DATOS){
	buffer = (char*)realloc(buffer, MENSAJE_TAMANIO_MAX+bytesRecibidos);
	if(msgrcv(id_cola, &recibido, sizeof(mensaje_t)-sizeof(long), idDestino, 0) == -1){
	    perror("Error al recibir mensaje: ");
	    exit(EXIT_ERROR_COLA);
	}
	memcpy(buffer+bytesRecibidos, recibido.buffer, MENSAJE_TAMANIO_MAX);
	bytesRecibidos += MENSAJE_TAMANIO_MAX;
    }
    tamanio = bytesRecibidos;
    return buffer;
}

char* ChatRecibirMensaje(ChatHandlerConversacion* conversacion, unsigned int &tamanio){
    if(!conversacion){
	log(fileno(stdout), "Error en ChatRecibirMensaje: el handler es nulo. Abortando.");
	exit(EXIT_HANDLER_NULO);
    }
    if(conversacion->ultimoMensaje)
	free(conversacion->ultimoMensaje);

    conversacion->ultimoMensaje = 
	Chat_RecibirMensaje(conversacion->idColaInBox, 
			    Chat_ObtenerId(conversacion->idConversacion, conversacion->numeroUsuario),
			    tamanio);

    if ( ((long*)conversacion->baseConversacion)[0] == 0)
	return conversacion->ultimoMensaje;
    return NULL;
}

static void Chat_EnviarMensaje(int id_cola, long idDestino, char* mensaje, unsigned int tamanio){
    mensaje_t a_enviar;
    int restante = tamanio;
    int copiado = 0;

    a_enviar.id = idDestino;
    while(restante > 0){
	if(restante > MENSAJE_TAMANIO_MAX){
	    a_enviar.flags = FLAG_MAS_DATOS;
	    memcpy(a_enviar.buffer,mensaje+copiado, MENSAJE_TAMANIO_MAX);
	    copiado += MENSAJE_TAMANIO_MAX;
	    restante -= MENSAJE_TAMANIO_MAX;
	}
	else{
	    a_enviar.flags = FLAG_FIN_DATOS;
	    memcpy(a_enviar.buffer,mensaje+copiado, restante);
	    memset(a_enviar.buffer+restante, 0, MENSAJE_TAMANIO_MAX-restante);
	    restante=0;
	}
	if(msgsnd(id_cola, &a_enviar, sizeof(mensaje_t)-sizeof(long), 0) == -1){
	    perror("Error al  enviar mensaje: ");
	    exit(EXIT_ERROR_COLA);
	}
    }
}


void ChatEnviarMensaje(ChatHandlerConversacion *conversacion, char* mensaje, unsigned int tamanio){
    if(conversacion){
	//TODO: Verificar privilegios
	int cantidad=0;
	long *lista = ChatListarUsuariosEnConversacion(conversacion, cantidad);
	
	while(cantidad > 0){
	    Chat_EnviarMensaje(conversacion->idColaOutBox,
			       Chat_ObtenerId(conversacion->idConversacion, lista[cantidad-1]),
			       mensaje, tamanio);
	    cantidad--;
	}
    }
    else{
	log(fileno(stdout), "Error en ChatEnviarMensaje: Handler nulo.");
	exit(EXIT_HANDLER_NULO);
    }
}
